package com.young.common.client.log.handler;

import com.young.common.client.log.configuration.MonitorLogConfiguration;
import com.young.common.util.*;
import com.young.interfaces.log.model.ApiMonitorDTO;
import com.young.interfaces.log.model.LogDTO;
import com.young.interfaces.log.service.ILogHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * http方式上报到监控中心
 * @author wangjiyu@imdada.cn
 * @create 2019/3/12
 */
@Component("report2Monitor4HttpLogHandler")
@ConditionalOnBean({MonitorLogConfiguration.class})
@ConfigurationProperties(prefix="young.log.client.report-2-monitor-4-http-log-handler")
public class Report2Monitor4HttpLogHandler implements ILogHandler {

    Logger logger = LoggerFactory.getLogger(Report2Monitor4HttpLogHandler.class);

    /**
     * 监控中心收集日志的url
     */
    private String logCollectUrl;
    /**
     * 批量发送数量
     */
    private int batchNum;
    /**
     * 收集接口监控指标的url
     */
    private String monitorCollectUrl;
    /**
     * 收集接口监控指标的周期,单位:分钟, 默认1分钟
     */
    private int monitorCollectCycle = 1;

    /**
     * 日志暂存区,收集够一定数量的日志后批量发送,减少网络交互
     */
    private LinkedList<LogDTO> LOG_STAGING = new LinkedList<LogDTO>();

    /**
     * 分钟耗时记录
     */
    private static final Map<Long, Map<String, List<Long>>> MS_MAP = new HashMap<Long, Map<String, List<Long>>>();
    /**
     * 异常数
     */
    private static final Map<Long, Map<String, Long>> EX_MAP = new HashMap<Long, Map<String, Long>>();
    /**
     * 记录上次性能监控时间
     */
    private static long lastMsPost = 0;
    /**
     * 分隔符
     */
    private static final String KEY_SEPARATOR = "@separator@";

    /**
     * 日志处理前置操作
     *
     * @param logDTO 待处理的日志传输对象
     */
    @Override
    public void handleBefore(LogDTO logDTO) {

    }

    /**
     * 日志处理
     *
     * @param logDTO
     */
    @Override
    public void handle(LogDTO logDTO) {
        if (logDTO == null) {
            return;
        }

        //链路日志处理
        this.handleLog(logDTO);

        //监控指标处理
        this.handleMonitor(logDTO);

    }

    /**
     * bean销毁处理程序
     */
    @PreDestroy
    public void destroy(){
        logger.info("[日志处理器-report2Monitor4HttpLogHandler] bean即将销毁,对未发送日志做最后处理.");

        if (!LOG_STAGING.isEmpty()) {
            Map<String, String> map = new HashMap<String, String>();
            map.put("logs", JsonUtil.toJson(LOG_STAGING));
            logger.info("[日志处理器-report2Monitor4HttpLogHandler] bean销毁前,发送收集到的日志({}条):{}", LOG_STAGING.size(), JsonUtil.toJson(LOG_STAGING));
            //发送日志
            HttpUtil.post(this.logCollectUrl, map);
        }


        //找出需要发送的数据
        List<ApiMonitorDTO> postList = new LinkedList<ApiMonitorDTO>();
        for (Map.Entry<Long, Map<String, List<Long>>> entry : MS_MAP.entrySet()) {
            Map<String, List<Long>> keyMs = entry.getValue();
            List<ApiMonitorDTO> list = this.buildMonitorDTO(keyMs, entry.getKey());//构造这分钟的监控数据
            if (list != null) {
                postList.addAll(list);
            }
        }

        //发送数据
        if (postList != null && !postList.isEmpty()) {
            Map<String, String> map = new HashMap<String, String>();
            map.put("monitors", JsonUtil.toJson(postList));
            logger.info("[日志处理器-report2Monitor4HttpLogHandler] bean销毁前待处理监控信息({}条):{}", postList.size(), JsonUtil.toJson(postList));
            //发送日志
            HttpUtil.post(this.monitorCollectUrl, map);
        }

        logger.info("[日志处理器-report2Monitor4HttpLogHandler] bean销毁前置处理完成,可以执行销毁程序.");
    }

    /**
     * 监控指标处理
     * @param logDTO
     */
    private void handleMonitor(LogDTO logDTO){
        if (logDTO.getBeginTime() == null ||  logDTO.getEndTime() == null || StringUtils.isBlank(logDTO.getKey())) {
            logger.info("[日志处理器-report2Monitor4HttpLogHandler] 方法起止时间或方法KEY无效.");
            return;
        }
        if (StringUtils.isBlank(this.monitorCollectUrl)) {
            logger.warn("[日志处理器-report2Monitor4HttpLogHandler] 无法提供监控指标处理服务:接口监控指标收集URL配置无效(young.log.client.report-2-monitor-4-http-log-handler.monitorCollectUrl),请检查配置参数是否正确");
            return;
        }

        long mm = Long.parseLong(DateUtil.dateFormat(logDTO.getBeginTime(), "yyyyMMddHHmm"));//分钟

        //找出需要发送的数据
        List<ApiMonitorDTO> postList = null;

        synchronized (MS_MAP) {
            if (!MS_MAP.containsKey(mm)) {
                MS_MAP.put(mm, new HashMap<String, List<Long>>());
            }

            //key与耗时
            Map<String, List<Long>> timeMs = MS_MAP.get(mm);
            String indexKey = logDTO.getAppName() + KEY_SEPARATOR + logDTO.getKey();//性能map的key
            if (!timeMs.containsKey(indexKey)) {
                timeMs.put(indexKey, new LinkedList<Long>());
            }

            //方法耗时集合
            List<Long> msList = timeMs.get(indexKey);
            //记录耗时
            msList.add(logDTO.getEndTime().getTime() - logDTO.getBeginTime().getTime());


            //存在异常时记录异常数
            if (logDTO.getException() != null) {
                if (!EX_MAP.containsKey(mm)) {
                    EX_MAP.put(mm, new HashMap<String, Long>());
                }
                Map<String, Long> exNum = EX_MAP.get(mm);//key与异常数
                if (!exNum.containsKey(indexKey)) {
                    exNum.put(indexKey, 0L);
                }
                exNum.put(indexKey, exNum.get(indexKey) + 1L);//异常数加1
            }

            long cur = System.currentTimeMillis();//当前时间戳

            if (lastMsPost == 0L) {
                lastMsPost = cur;
                return;
            }

            //距离上次发送超过N分钟则发送
            if ((cur - lastMsPost) > this.monitorCollectCycle * 60L * 1000L) {
                lastMsPost = cur;//记录上次发送时间
                long curMin = Long.parseLong(DateUtil.dateFormat(new Date(cur), "yyyyMMddHHmm"));//当前分钟
                postList = new LinkedList<ApiMonitorDTO>();
                for (Iterator<Map.Entry<Long, Map<String, List<Long>>>> it = MS_MAP.entrySet().iterator(); it.hasNext();) {
                    Map.Entry<Long, Map<String, List<Long>>> entry = it.next();
                    //这分钟之前的数据要收集
                    if (entry.getKey() < curMin) {
                        Map<String, List<Long>> keyMs = entry.getValue();
                        List<ApiMonitorDTO> list = this.buildMonitorDTO(keyMs, entry.getKey());//构造这分钟的监控数据
                        if (list != null) {
                            postList.addAll(list);
                        }
                        it.remove();//删除无效数据,若后续的发送失败则会丢掉这部分数据
                    }
                }

                if (postList.isEmpty()) {
                    logger.info("[日志处理器-report2Monitor4HttpLogHandler] 前面{}分钟内未收集到任何接口监控数据.", this.monitorCollectCycle);
                }
            }
        }

        //发送数据-放到同步块外面,防止同一时刻的日志处理阻塞在同步块处
        if (postList != null && !postList.isEmpty()) {
            Map<String, String> map = new HashMap<String, String>();
            map.put("monitors", JsonUtil.toJson(postList));
            logger.debug("[日志处理器-report2Monitor4HttpLogHandler] 发送收集到的方法性能监控({}条).", postList.size());
            //发送日志
            HttpUtil.post(this.monitorCollectUrl, map);
        }
    }

    /**
     * 构建监控对象
     * @param keyMs
     * @param minnute
     * @return
     */
    private List<ApiMonitorDTO> buildMonitorDTO(Map<String, List<Long>> keyMs, long minnute){
        if (keyMs == null || keyMs.isEmpty()) {
            return null;
        }
        List<ApiMonitorDTO> monitorList = new ArrayList<ApiMonitorDTO>();
        for (Map.Entry<String, List<Long>> entry : keyMs.entrySet()) {
            if (entry.getValue() == null || entry.getValue().isEmpty()) {
                continue;
            }
            List<Long> list = entry.getValue();
            Collections.sort(list);//排序
            ApiMonitorDTO apiMonitorDTO = new ApiMonitorDTO();
            String[] key = entry.getKey().split(KEY_SEPARATOR);
            apiMonitorDTO.setAppName(key[0]);
            apiMonitorDTO.setKey(key[1]);
            apiMonitorDTO.setMinute(minnute);
            apiMonitorDTO.setTp50(list.get((int)(list.size() * 0.5)));
            apiMonitorDTO.setTp90(list.get((int)(list.size() * 0.9)));
            apiMonitorDTO.setTp99(list.get((int)(list.size() * 0.99)));
            apiMonitorDTO.setTp999(list.get((int)(list.size() * 0.999)));
            apiMonitorDTO.setMinMs(list.get(0));
            apiMonitorDTO.setMaxMs(list.get(list.size() - 1));
            apiMonitorDTO.setCallNum((long) list.size());
            apiMonitorDTO.setServerIp(IPUtil.getLocalIp());
            long allMs = 0;
            for (Long ms : list) {
                allMs += ms;
            }
            apiMonitorDTO.setAvgMs(allMs / list.size());
            apiMonitorDTO.setAvailability(100.0F);//默认100

            Map<String, Long> exMap = EX_MAP.get(minnute);
            if (exMap != null) {
                Long exNum = exMap.get(entry.getKey());
                if (exNum != null) {
                    apiMonitorDTO.setAvailability((float) (exNum / list.size()));
                }
            }
            monitorList.add(apiMonitorDTO);
        }
        return monitorList;
    }

    /**
     * 链路日志处理
     * @param logDTO
     */
    private void handleLog(LogDTO logDTO){
        if (StringUtils.isBlank(this.logCollectUrl)) {
            logger.warn("[日志处理器-report2Monitor4HttpLogHandler] 无法提供日志处理服务:监控中心日志收集URL配置无效(young.log.client.report-2-monitor-4-http-log-handler.logCollectUrl),请检查配置参数是否正确");
            return;
        }

        int num = this.batchNum > 1 ? this.batchNum : 1;

        List<LogDTO> list = null;//准备发送的日志

        if (num == 1) {//单条收集时,直接放入待收集列表
            list = new ArrayList<LogDTO>(1);
            list.add(logDTO);
        } else {//批量收集
            synchronized (LOG_STAGING) {
                //放入暂存区
                LOG_STAGING.add(logDTO);
                if (LOG_STAGING.size() >= num) {//达到指定数量后收集
                    list = new ArrayList<LogDTO>(LOG_STAGING);
                    LOG_STAGING.clear();
                }
            }
        }

        if (list != null && !list.isEmpty()) {
            Map<String, String> map = new HashMap<String, String>();
            map.put("logs", JsonUtil.toJson(list));
            logger.debug("[日志处理器-report2Monitor4HttpLogHandler] 发送收集到的日志({}条).", list.size());
            //发送日志
            HttpUtil.post(this.logCollectUrl, map);
        }
    }

    /**
     * 日志处理后置操作
     *
     * @param logDTO
     */
    @Override
    public void handleAfter(LogDTO logDTO) {

    }

    public int getBatchNum() {
        return batchNum;
    }

    public void setBatchNum(int batchNum) {
        this.batchNum = batchNum;
    }

    public String getLogCollectUrl() {
        return logCollectUrl;
    }

    public void setLogCollectUrl(String logCollectUrl) {
        this.logCollectUrl = logCollectUrl;
    }

    public String getMonitorCollectUrl() {
        return monitorCollectUrl;
    }

    public void setMonitorCollectUrl(String monitorCollectUrl) {
        this.monitorCollectUrl = monitorCollectUrl;
    }

    public int getMonitorCollectCycle() {
        return monitorCollectCycle;
    }

    public void setMonitorCollectCycle(int monitorCollectCycle) {
        this.monitorCollectCycle = monitorCollectCycle;
    }
}
